home *** CD-ROM | disk | FTP | other *** search
/ develop, the CD; issue 1 / Apple_Develop_1989.bin / Offscreen / FracApp 2.0B3 / UFracApp.inc1.p < prev    next >
Text File  |  1989-09-22  |  84KB  |  1,899 lines

  1. { AutoFracApp Color:
  2.     Copyright 1988 by Bob.  All rights reserved, since Bob has all rights.
  3.     February 1, 1988. 
  4.     Written by Bo3b Johnson of Developer Technical Support. }
  5.     
  6. { Version 2.0B3 for MacApp 2.0 + OffScreen World Support by Guillermo Ortiz 07/03/89}
  7. { To look for the changes search for the keywords GetGWorld, LockPixels, UnlockPixs, NewGWorld, 
  8. etc. The only proc that needed replacement was BuildOffWorld check it up!}
  9.  
  10. { The following is a list of features or bug fixes that could be added to the program:
  11.   *** Check the segmentation.
  12.   *** Make it run crashless using temp documents to store partial fractals.
  13.   *** Updates could be cleaner so no partial fractals are displayed.
  14.   *** Override window.updateevent so we can avoid the EraseRect on updates.
  15.   *** Draw selection rect in offscreen, copy up to screen for flicker free selection.
  16.   *** Crash on 3 monitor system during window drag.
  17.   *** Could set the bytes directly in offscreen PixMap, skip using MoveTo:Line. 
  18.   *** Copy up from small picture up to big screen gets garbage, src rect too big?
  19.   *** Allow a way to Zoom in using coordinates.
  20.   *** Allow the user to set the colors used in display.
  21.   *** Bigger penSize for fast, lo-res fractals.  Allow user to set size of pen.
  22.   *** Some things for the reader to do to modify the program. 
  23.   }
  24.  
  25. { 3/27/89:  Fixed the bug with AutoSave that would draw the window frame into
  26.     the offscreen port, then save the document that way.  It was leaving the gDevice
  27.     set offscreen when SetWTitle was called in CalcCity. }
  28.  
  29. { Where does it fit:
  30.     This is a series of sample programs for those doing development
  31.     using Color QuickDraw.  Since the whole color problem depends
  32.     upon the exact effect desired, there are a number of answers
  33.     to how to use colors, from the simple to the radically complex.
  34.     These programs try to cover the gamut, so you should use 
  35.     which ever seems appropriate.  In most cases, use the simplest
  36.     one that will give the desired results.  The compatibility
  37.     rating is from 0..9 where low is better.  The more known risks 
  38.     there are the higher the rating.
  39.     
  40.     
  41.     The programs (in order of compatibility):
  42.     
  43.         SillyBalls:
  44.             This is the simplest use of Color QuickDraw, and does
  45.             not use the Palette Manager.  It draws randomly colored
  46.             balls in a color window.  This is intended to give you
  47.             the absolute minimum required to get color on the screen.
  48.             Written in straight Pascal code.
  49.             Compatibility rating = 0, no known risks.
  50.         
  51.         FracAppPalette:    (***)
  52.             This is a version of FracApp that uses only the Palette
  53.             Manager.  It does not support color table animation
  54.             since that part of the Palette Manager is not sufficient.
  55.             The program demonstrates a full color palette that is
  56.             used to display the Mandelbrot set.  It uses an offscreen
  57.             gDevice w/ Port to handle the data, using CopyBits to
  58.             draw to the window.  The Palette is automatically 
  59.             associated with each window.  The PICT files are read
  60.             and written using the bottlenecks, to save on memory
  61.             useage.
  62.             Written in MacApp Object Pascal code.
  63.             Compatibility rating = 0, no known risks.
  64.         
  65.         TubeTest:
  66.             This is a small demo program that demonstrates using the
  67.             Palette Manager for color table animation.  It uses a 
  68.             color palette with animating entries, and draws using the
  69.             Palette Manager.  There are two circles of animating colors
  70.             which gives a flowing tube effect.  This is a valid case
  71.             for using the animating colors aspect of the Palette Manager,
  72.             since the image is being drawn directly.
  73.             Written in straight Pascal code.
  74.             Compatibility rating = 0, no known risks.
  75.         
  76.         FracApp:
  77.             This is the `commercial quality' version of FracApp.  This
  78.             version supports color table animation, using an offscreen
  79.             gDevice w/ Port, and handles multiple documents.  The
  80.             CopyBits updates to the screen are as fast as possible.  The
  81.             program does not use the Palette Manager, except to
  82.             provide for the system palette, or color modes with less than
  83.             255 colors.  For color table animation using an offscreen
  84.             gDevice w/ Port, it uses the Color Manager and handles the
  85.             colors itself.  Strict compatibility was relaxed to allow for
  86.             a higher performance program.  This is the most `real' of the
  87.             sample programs.
  88.             Written in MacApp Object Pascal code.
  89.             Compatibility rating = 2.  (nothing will break, but it may not
  90.                 always look correct.)
  91.         
  92.         FracApp300:
  93.             This doesn't support colors, but demonstrates how to create and
  94.             use a 300 dpi bitmap w/ Port.  The bitmap is printed at full
  95.             resolution on LaserWriters, and clipped on other printers (but
  96.             they still print).  It demonstrates how to use a high resolution
  97.             image as a PICT file, and how to print them out.
  98.             Written in MacApp Object Pascal code.
  99.             Compatibility rating = 1.  (The use of PrGeneral is slightly 
  100.                 out of the ordinary, although supported.)
  101. }
  102.  
  103. { Reasons for this version of reality (the strategy):
  104.     The main idea behind this program is to allow you to create and fool around 
  105.     with the Mandelbrot set, using this number cruncher wizzo computer we got
  106.     here.  While we are making these documents, we also throw in a couple of
  107.     special effects to make it more fun, like the special color mapping.  
  108.     This program is specifically intended to be a sample program, and as such
  109.     takes no compatibility risks.  This means it is not the highest performance, it
  110.     is the safest.  No intentional shortcuts were taken
  111.     in the program, but some things were left out due to time constraints.  Like
  112.     all real programs, some people will like it and some people will hate it.  I
  113.     hope you like it, but if you don't, send me some mail telling me why.
  114.     
  115.     This version of the program does not support color table animation, even 
  116.     though this was one of the major effects the program was to use for the
  117.     most interesting effect.  The Palette Manager is not sufficient to perform
  118.     the color table animation for multiple documents, so for this version it
  119.     was removed.  This is the most compatible way of doing things, although
  120.     the most lame as well.
  121.     
  122.     The overall structure of the program is to have the MacApp Document object
  123.     handle all the data.  This includes an offscreen gDevice and port that are
  124.     the actual fractal data.  The View object uses the Document's data to draw
  125.     into the window visible to the user.  The Document does all the work of
  126.     calculating new fractals during Idle times.  It also handles saving the
  127.     data to disk and reading it back.  The data files are PICT so as to be as
  128.     compatible as we can.   The program handles zooming in to
  129.     see closer views of the environment, using selection rectangles as you
  130.     would expect.  This whole block of comments at the beginning are intended
  131.     to describe some more macroscopic problems and structure.  In the code 
  132.     itself you will find the tactical comments dealing with how a specific
  133.     operation is done.  
  134.     
  135.     The fractal calculation is done by the CalcCity routine of the Document.  The
  136.     Application object gets the DoIdle call, and he calls each document to 
  137.     have a pixel calculated in each document.  The calculation is done a pixel
  138.     at a time so that it can be done in the background with no visible effect
  139.     on the foreground app.  As each horizontal line of data is finished it is 
  140.     updated to the screen.  The algorithm is very simple.
  141.     
  142.     Allocating and using an offscreen gDevice and port are found in the Document
  143.     object, as the BuildOffWorld method.  The port offscreen is used as a drawing
  144.     environment once a pixel is calculated, as seen in DoIdle for the Document.
  145.  
  146.  
  147.     If your program does not need to use color table animation, then you should
  148.     use the Palette Manager.  It does a good job at handling the color environment
  149.     when the clients merely need to use colors.  In this version of the program,
  150.     we avoid color table animation, since it would require some compatability
  151.     risks in order to use it effectively.  The palette used here is our group of
  152.     colors, associated with each window, making the fractals look 'right'
  153.     whenever we get an update event.  The color mapping from the offscreen
  154.     gDevice w/ Port to the screen is done automatically by CopyBits, so we
  155.     don't have to do anything special to make it work.  Using the Palette 
  156.     Manager makes for the simplest code.  This version gives acceptable
  157.     performance when using system 6.0 or QuickerDraw.  In addition the colors
  158.     displayed should always look correct, since the Palette Manager will try
  159.     to keep the colors displayed as accurate as possible.  Even when in the
  160.     background, or on other monitors, the colors displayed should look 
  161.     close to the normal set.  For simpler code, the Color Search Proc that made 
  162.     the zebra display was removed.  The palette has the system palette as the
  163.     first 16 colors, which will be used in the lesser color modes.  This is to
  164.     avoid forcing a large group of one color onto the monitor, which will look 
  165.     as bad as having the system palette.  Rather than have the PM make 
  166.     everybody look bad in lesser modes, we will hopefully make other programs
  167.     look OK by ensuring those colors are there.
  168.     
  169.     When we set up the offscreen gDevice, we set it up using a 3 bit iTable to save
  170.     on memory that isn't used.  The offscreen device is not filled using color 
  171.     commands, but is filled using an indexed mode instead.  This means no color
  172.     mapping is necessary for that as a destination, so there is no point in taking
  173.     up another 4K per document that is not used.
  174.     
  175.     When a document is saved the color table from the offscreen gDevice is used.
  176.     This is the clut resource that is used uniformly throughout the program.  If
  177.     you hate my choice of colors, you can change the clut to something else and
  178.     all should work the same (except old documents will map into something else).
  179.     
  180.     The program currently uses ScreenBits.Bounds as the determining size of the
  181.     view and thus the fractal that is calculated.  This is OK, but a better approach
  182.     would be to allow the user to specify the limits of the Fractal they want.  This
  183.     involves adding another dialog and save defaults type of feature but is a better
  184.     way to solve the problem since it is not obvious what the best size would be.
  185.     ScreenBits.bounds is easy to use, but whenever there is no obvious best answer
  186.     it is always better to let the user decide instead, that way they can't bitch.
  187.     
  188.     Another goal of course was to get this thing done.  A goal that tends to slide
  189.     away as more things are added to the program, so in true Macintosh style,
  190.     the 1.0 version of the program is somewhat limited, and may not be fully
  191.     debugged.  Some things specifically left out: printing the documents to 
  192.     a LaserWriter with grey scales instead, using temporary documents to 
  193.     make the program crashless (so you can start up where you left off, saving
  194.     the computation it took to get there), an option to make the `pen' size bigger
  195.     so you can do a low-res fractal to begin with.  These things are all admirable 
  196.     features to add, but you have to finish version 1.0 sometime, so this was it.  
  197.     These other things will be added if possible.
  198.     
  199.     Carefully watch the 881 flag with this MPW business.  There are a number of
  200.     ridiculous problems associated with its use.  In particular the $LOAD files
  201.     are dangerous to use with 881 and the combination of the two will often 
  202.     end up compiling successfully into a program that is garbage and will
  203.     crash upon running it.  This, remind yourself, is a feature of the most 
  204.     powerful development system around.  In order to build currently, the main
  205.     program MFracApp.p should be compiled with new $LOAD files and the 881
  206.     flag turned off.  All of the MacApp sources should be compiled with a new
  207.     $LOAD file and 881 turned off.  The last step should be to compile the UFracApp.p
  208.     with the 881 flag turned on, and with new $LOAD files as well.  Beware or
  209.     be prepared to spend a lot of time on something silly.  To solve this 
  210.     problem the Make file used with FracApp has a specific compile rule for 
  211.     the UFracApp file.  UFracApp also uses its own $LOAD file, that is different
  212.     from the MacApp and MFracApp ones.  The 881 flag is not specifically
  213.     required, but it makes code that uses the 881 directly instead of going
  214.     through SANE for a speed up of about 10 times.  Since we are speed freaks
  215.     here as well, the 881 option had to be used.  Given the problems, I would
  216.     probably skip the 881 option and do the time critical pieces in assembler.
  217.     
  218.     If you want to know how long a fractal took to calculate there is a time
  219.     stamp saved in the file header.  It is no longer drawn in the window since
  220.     it is not accurate when the program has run in the background or if there
  221.     are multiple documents open.  This could be added again if desired.
  222.     
  223.     Notably I am aware of the fact that this program does not really calculate
  224.     Fractals.  Actually it calculates and displays the Mandelbrot set which is
  225.     not self-similar so it cannot really be called a fractal.  It is distressing to
  226.     add to the confusion as to what fractals are, but it is too late.  For more
  227.     information on Fractals and the Mandelbrot set (no umlaut on the o), you
  228.     could see Mandelbrot's book `The Fractal Geometry of Nature', but it is 
  229.     pretty mathematical and not all that helpful.  A better source is the
  230.     Peitgen-Richter book `The Beauty of Fractals', which has a do it yourself
  231.     section in the back.
  232.     
  233.     The program is structured primarily around the document.  The document is
  234.     the object to create and maintain the offscreen gDevice & port.  The document
  235.     converts the offscreen data into a PICT file when saved or restored.  The
  236.     document also does the calculation of the fractal, keeping the offscreen data
  237.     up to data as it goes along.  The view only handles taking the document data
  238.     and displaying it.
  239.     
  240.     For the zoom operation, there was no really great way to handle the new
  241.     document case based on another document.  This is a little strange to be 
  242.     doing, and the structure of MacApp was such that we couldn't get to the 
  243.     data desired at the right time.  The logical place to put it in at 
  244.     DoMakeDocument was too early to have the gDevice allocated and ready
  245.     to start more stuff.  The problem was resolved by using global variables
  246.     to transmit the information to the other piece of the program that
  247.     might need it.  Essentially DoInitialState decides if this is a brand new
  248.     base level document or a zoom in based on the state of the global variables.
  249.     
  250.     The MacApp memory management approach is used as well.  The big pieces
  251.     used by the Documents come out of permanent memory, helping to avoid
  252.     a crash from no memory.  When we allocate something that will be thrown
  253.     away immediately, like a spare color table or something, it of course
  254.     comes out of normal memory.  We did not do a full blown memory analysis
  255.     of the program since it is such a memory hog anyway.  The mem! resource
  256.     is set up in a form that is roughly close (a bit high) without trying to be
  257.     extra accurate.  When a document takes 400K of RAM to open it hardly 
  258.     seems relevant to make sure the mem! is accurate to 2K.  Because of this
  259.     somewhat cavalier approach you may not be able to open a document in
  260.     a few cases where you really should be able to.  The mem! in use of 40K
  261.     is close with a +2K/-10K error on how big it should really be.
  262.     
  263.     The time stamp as the elapsedTime field in the FracHeader is not fully
  264.     accurate, so it is no longer displayed.  Currently it is set as an elapsed
  265.     time from when it was started to when it ended, which has a number
  266.     of problems.  This will be made more accurate in the future, probably
  267.     using a calculation that gives us a once through the loop calculation
  268.     in units of TimeDBRA, so we can be more machine independent.
  269.     
  270.     The QuickDraw BottleNecks are used to both read and write the actual
  271.     fractal data from/to the disk.  This is done since the data in the document
  272.     may be very large (100K) and if we just spool the data from the file
  273.     we don't actually have to use that extra hunk of memory.  We have to read
  274.     the data anyway, so we go ahead and just read it in as we play it back.
  275.     As it is played back it goes into the offscreen gDevice's pixMap, so we
  276.     have the data to display.  No memory hit for the document is a big win.
  277.     When writing the data, the same thing is true so we don't have to have
  278.     a huge handle to hold the picture data itself.  We also avoid the problem
  279.     of not having enough memory to create the picture in the first place,
  280.     making a document unsaveable.  That is particualarly annoying, and
  281.     is easy to avoid using the spooling approach.   The drawback here is that
  282.     the spooling process slows down opening and closing documents rather
  283.     severely, since it breaks up a long read/write operation into hundreds of
  284.     small ones.
  285.     
  286.     With thanks to Skippy Blair for the discussions of color QuickDraw and the 
  287.     Palette Manager.  Thanks to Darin Adler for further discussion of the Palette
  288.     Manager and for good suggestions on making it more MacApp friendly.
  289.     }
  290.     
  291. Type    PageRec = Record
  292.                 currentH,
  293.                 currentV,
  294.                 maxH,
  295.                 maxV:            Integer;
  296.                 RealMin,
  297.                 RealMax,
  298.                 ImagMin,
  299.                 ImagMax:        Extended;                { for the current document. }
  300.             End;
  301.             PageRecPtr = ^PageRec;
  302.  
  303.  
  304.     { Global variables.  }
  305. VAR
  306.     gStaggerCount:     INTEGER;                { for staggering windows. }
  307.     gOurColors:            CTabHandle;            { color table we use as a source for documents. }
  308. {    gRealMin,
  309.     gRealMax,
  310.     gImagMin,
  311.     gImagMax:            Extended;                { used for zooming in operation. }
  312.  
  313.         { The next globals are used for the QuickDraw bottlenecks when reading or
  314.             writing a picture to disk.  These are needed, since the bottlenecks cannot
  315.             be owned procedures. }
  316.     gPictSize:            LongInt;                { number of bytes used for saving a PICT. }
  317.     gPictError:            OSErr;                    { do some error handling in bottleneck. }
  318.     gPictRefNum:        Integer;                { Need the refnum of the open file too. }
  319.     gPictHandle:        PicHandle;            { for reading/writing a picture. }
  320.  
  321.     gPageRec:            PageRec;                { current and total page count from 'page' resource }
  322.     
  323.         
  324.  
  325. { Set some compiler options that we desire for the main body of code only.  You
  326.     might wish to leave on the range checking, but I was not satisfied with having
  327.     to push and pop in the code, and was not pleased with the slowdown in performance. 
  328.     These can be dangerous to turn off, especially the $H.  For those who use MPW
  329.     more than I, you probably want to make these settable from the command line,
  330.     or use the MacApp debug or no-debug compile time variables.  }
  331. {$PUSH}        { Save the compiler state before we change it. }
  332. {$D+}            { Debugging labels on for the code here. }
  333. {$R-}            { No range checking to make things faster. }
  334. {$OV-}            { No overflow checking either. }
  335. {$H-}            { No handle checking to avoid compiler complaints on WITHs.  Be careful. }
  336. {$N+}    
  337.  
  338. {-------------------------------  Application  -------------------------------}
  339.  
  340. PROCEDURE TFracAppApplication.IFracAppApplication(itsMainFileType: OSType);
  341.  
  342. VAR            pageH:                Handle;
  343.  
  344. BEGIN
  345.     gStaggerCount := 0;
  346.     IApplication(itsMainFileType);
  347. {    fIdleFreq := 0;                            { say we need Idle time calls to TApp.DoIdle }
  348. (*    gRealMin := 0;                            { must be set to empty to start with. }
  349.     gRealMax := 0;                            { so we do normal document open. }
  350. *)    
  351.         { Get the current page count of a multiple page operation. }
  352.     pageH := GetResource('page', kPageId);
  353.     FailNil (pageH);
  354.     gPageRec := PageRecPtr(pageH^)^;
  355.     
  356.         { Now allocate a color table that we will use whenever we create a new document
  357.             or need to compare colors.  This is so we have the same color table for each
  358.             document, as well having the ctSeed the same for them all. }
  359.     gOurColors := GetCTable(kClut);                    { install our new desired one from clut }
  360.     FailNil (gOurColors);
  361.     
  362. END;        { TFracAppApplication.IFracAppApplication }
  363.  
  364.  
  365. (*    { The Close method to allow us to clean up before the regular TApplication.Close
  366.         gets called.  We OverRide it so we get control first.   We need to remove the
  367.         coHandler for the idle processing before the regular close runs, since it will
  368.         dispose all coHandlers, and it isn't cool to have the Tapp object die to soon.
  369.         So, remove the cohandler here, but watch for a failure like a cancel out of a
  370.         save dialog.  If they cancel, staying in the app, we need to reinstall the co
  371.         handler, which explains the failure handler here. }
  372. PROCEDURE TFracAppApplication.Close;  OVERRIDE;
  373. VAR        fi:                FailInfo;
  374.     
  375.     PROCEDURE DeathClose (error: OSErr; message: LONGINT);
  376.     BEGIN
  377.         InstallCohandler(SELF, TRUE);        { ReInstall coHandler, we are staying. }
  378.     END;
  379.  
  380. BEGIN
  381.     CatchFailures(fi, DeathClose);    
  382.     InstallCohandler(SELF, FALSE);        { Remove coHandler, about to leave. }
  383.     INHERITED Close;
  384.     Success (fi);
  385. END;
  386. *)
  387.  
  388.     { OK, this is where a new document gets created.  This does the init for the
  389.         document object itself.  After it is done, the view and window can
  390.         be created, relying upon the data in the document. }
  391. FUNCTION  TFracAppApplication.DoMakeDocument(itsCmdNumber: CmdNumber):
  392.         TDocument; OVERRIDE;
  393.         
  394. VAR        aFracAppDocument:    TFracAppDocument;
  395.  
  396. BEGIN
  397.     { Allocate and initialize the document}
  398.     New(aFracAppDocument);
  399.     FailNil(aFracAppDocument);
  400.  
  401.     { Now initialize the document fields, and set up the global state of the fractal
  402.         to a default set of the starting fractal. }
  403.     aFracAppDocument.IFracAppDocument;
  404.  
  405.     { We successfully created a document so we can return the document object for
  406.         use by the application. }
  407.     DoMakeDocument := aFracAppDocument;
  408.     
  409. END;    { TFracAppApplication.DoMakeDocument }
  410.  
  411.  
  412. (*    { Performs Idle time processing for the application.  This will do the
  413.         fractal calculation during the idle times.  It will allow each open
  414.         document a chance to calculate.  The CalcCity is a method owned by
  415.         each document that will get a call from the ForAllDocumentsDo. 
  416.         The documents don't use the DoIdle routine since we want each
  417.         open document to get time, not just the one in the target chain. }
  418. PROCEDURE  TFracAppApplication.DoIdle (phase: IdlePhase); OVERRIDE;
  419.         VAR        KillIt:    Boolean;
  420.  
  421.         { Give each document some CPU time. }
  422. *)(*    PROCEDURE DoFractalCalc(aDocument: TFracAppDocument);
  423.     
  424.     BEGIN
  425.         aDocument.CalcCity;                        { give the document its time to calc. }
  426.     END;
  427. *)(*
  428. BEGIN
  429. {    WriteLn (' Idle appy');  }
  430.         { Send the message to each open document to calculate the next pixel. }
  431. {    IF phase = IdleContinue  THEN  ForAllDocumentsDo (DoFractalCalc);
  432. }    IF gDocument <> NIL  THEN  BEGIN
  433.         KillIt := TFracAppDocument(gDocument).CalcCity;
  434.         IF KillIt THEN BEGIN
  435.             gDocument.Close;
  436.             IF (gPageRec.currentV <> 0) and (gPageRec.currentH <> 0) THEN 
  437.                 OpenNew(cNew);
  438.         END;
  439.     END;
  440. END;        { TFracAppApplication.DoIdle }
  441. *)
  442.  
  443.  
  444. {-------------------------------  Document  -------------------------------}
  445.  
  446.     { An auxiliary method to set up the step constants for the fractal calculation.
  447.         It is external since we need to set up the constants when we create a new
  448.         fractal as a zoom in.   Sets up the width/height of fractal, the delta in each
  449.         axis as a real number, and ensures that the starting min/max values for
  450.         the figure are set to supply a 1:1 aspect ratio.  The step constants are 
  451.         zeroed to start the fractal anew.  Allocates no memory. }
  452. PROCEDURE  TFracAppDocument.SetUpConstants;
  453.  
  454. BEGIN
  455.     WITH fFracHeader  DO  BEGIN
  456.         { Set up the iterations by calculating up the step constants, and the 
  457.             edges of the view area in pixels. }
  458.     
  459.         plotWidth := (calcRect.Right - calcRect.Left);
  460.         plotHeight := (calcRect.Bottom - calcRect.Top);        
  461.         deltaP := (realMax-realMin)/(plotHeight-1);
  462.         deltaQ := (imagMax-imagMin)/(plotWidth-1);
  463.     
  464.         { Force aspect ratio 1:1, making delta smallest of two.  This effectively grows
  465.             one side or the other out, like rMax/iMax becoming bigger number. }
  466.         IF deltaP > deltaQ  THEN  BEGIN
  467.             deltaQ := deltaP;
  468.             imagMax := deltaQ * (plotWidth-1) + imagMin;     { new maximum for q }
  469.         END   { grow the q side }
  470.         ELSE  BEGIN
  471.             deltaP := deltaQ;
  472.             realMax := deltaP * (plotHeight-1) + realMin;         { new maximum for p }
  473.         END;  { grow the p side }
  474.     
  475.         { Now start the counters at zero, as the edge of the area to calc. }
  476.         curCol := 0;  curRow := 0;
  477.         
  478.         { And the elapsed time is zero of course, since we are just starting. }
  479.         elapsedTime := 0;
  480.     END;        { WITH fractalDocument }
  481. END;        { SetUpConstants }
  482.  
  483.  
  484.     { Utility method to build the offscreen gDevice and offscreen Port that is used for
  485.         the document data.  This happy fellow will allocate huge old hunks of Ram for
  486.         the document and set up the initial state of the gDevice with the right color
  487.         table and so on.  This is done as a utility routine since we don't know in advance
  488.         how big the gDevice will be, and we want to make it as big as it was when the
  489.         document was saved, reading it from the header.  If we are making a new 
  490.         document, the DoInitialState will call with the screen rectangle. }
  491.         
  492. (* PROCEDURE  TFracAppDocument.BuildOffWorld (sizeOfDoc: Rect);
  493.  
  494. VAR        oldPerm:             Boolean;
  495.             dummy:                Boolean;
  496.             docW, docH:        LongInt;
  497.             fi:                     FailInfo;
  498.             currDevice:        GDHandle;
  499.             currPort:            GrafPtr;
  500.             Erry:                    OSErr;
  501.                 
  502.         { This is the error handler for when we get errors while making a new document,
  503.             typically like running out of memory.  Since the Free method for the document
  504.             will get called we don't have to chuck the things that normally get killed. 
  505.             Just set allocation back to normal (for the error message itself), the drawing 
  506.             environment back to normal and return.  }
  507.     PROCEDURE DeathDocument (error: OSErr; message: LONGINT);
  508.     
  509.     BEGIN
  510.         oldPerm := PermAllocation (oldPerm);    { Set memory back to previous. }
  511.         
  512.         SetGDevice (currDevice);                        { Set device back to main, just in case. }
  513.         SetPort (currPort);
  514.     END;
  515.  
  516. BEGIN
  517.     currDevice := GetGDevice;                        { save current for error handling. }
  518.     GetPort(currPort);
  519.     
  520.     { The memory used creating the view must be out of permanent memory, it is too 
  521.         big.  Any failure to get it from permanent memory will invoke the error handler. }
  522.     oldPerm := PermAllocation (TRUE);
  523.  
  524.     CatchFailures(fi, DeathDocument);            { any failures, must be cleaned up. }
  525.  
  526.     { Let's set up the size of the rectangle we are using for the document. }
  527.     docW := sizeOfDoc.right - sizeOfDoc.left;
  528.     docH := sizeOfDoc.bottom - sizeOfDoc.top;
  529.  
  530.  
  531.     { Now try to set up the offscreen bitMap (color).  If we fail we have to split,
  532.         and we might since we may not have 300K or more (basically a full screen
  533.         worth, which is unlikely to be less than 300K) for the pixMap.  Each document
  534.         on screen will have a full pixMap for it.  Allocate a full screen size buffer in
  535.         8 bit depth.  Also make it into a color port so we can draw into it normally and 
  536.         use it as a source for CopyBits.  Requires 8 bits deep for the number of colors,  
  537.         and sets up a buffer with that in mind, that is full docRect size with
  538.         one byte per pixel as 8 bit mode.  This is width x height.  8 bits/byte. }    
  539.     fBigBuff := NewPtr (docW * docH);
  540.     FailMemError;                                                { couldn't get it we die. }
  541.  
  542.  
  543.     { OK, now we get wacko.  We need to create our own gDevice, since we want to have
  544.         an offscreen device.  This needs to be done so that we have full control over the
  545.         color table used, in order to save full 8 bit documents, even if we aren't in 8 bit
  546.         mode when we save.  So...  We will start by creating a NewGDevice, that will 
  547.         allocate a temporary ITable, and PixMap with partial colorTable;  change the 
  548.         fields of the device's pixMap to our bitMap, with right size, depth, and rowbytes; 
  549.         init the fields of that device, including changing the color table to our color 
  550.         table created from our clut;  set that gDevice as the current one; then do the 
  551.         OpenCPort which will use the current gDevice to make its PixMap and color table;
  552.         When we go to draw or save the data in the offscreen buffer,
  553.         we need to set the current device so we use our color table, making all the
  554.         colors come out right.  }
  555.             
  556.     { Now we need to do the piece to make an offscreen gDevice that is not connected
  557.         to the screen.  Allocate a new one, with stub pixMap. }
  558.     fDrawingDevice := NewGDevice (0, -1);            { -1 means unphysical device.  }
  559.     FailNIL (fDrawingDevice);                                { If we failed, error out. }
  560.     
  561.     { Now init all the fields we can in the gDevice Record, since it comes uninitialized. }
  562.     HLock ( Handle(fDrawingDevice) );
  563.     WITH  fDrawingDevice^^  DO  BEGIN
  564.         gdId := 0;                                                        { no ID for search & complement procs }
  565.         gdType := clutType;                                        { color table type fer sure. }
  566.         
  567.         { Get the color table for the offscreen gDevice.  This is a copy of the global
  568.             color table we created early on. }
  569.         DisposCTable (gdPMap^^.pmTable);                { kill the stub that is there. }
  570.         gdPMap^^.pmTable := gOurColors;                    { make a copy of our global color table. }
  571.         Erry := HandToHand (Handle(gdPMap^^.pmTable)); { and stick it into this gDevice too. }
  572.         FailOSErr (Erry);                                            { if not possible, blow out. }
  573.         
  574.         { build a new iTable for this device, based on the new color table.  3 bit res to
  575.             save on memory since we don't need the iTable for our stuff. }
  576.         MakeITable (gdPMap^^.pmTable, gdITable, 3);
  577.         FailOSErr (QDError);                                        { no memory, we can leave here. }
  578.         
  579.         gdResPref := 3;                                                { preferred resolution in table. }
  580.         gdSearchProc := NIL;                                        { no search proc. }
  581.         gdCompProc := NIL;                                        { no complement proc. }
  582.         { Set the gdFlags to be: color, ramInit, noDriver, screenActive }
  583.         gdFlags := 2**0 + 2**10 + 2**14 + 2**15;    { set each bit we need. }
  584.         
  585.         { Now set up the fields in the offscreen PixMap correctly. }
  586.         gdPMap^^.baseAddr := fBigBuff;                        { The base address is our buffer. }
  587.         gdPMap^^.bounds := sizeOfDoc;                        { bounding rectangle to our device. }
  588.         { one byte per pixel horizontally is rowBytes.  + $8000 to make it color port. }
  589.         gdPMap^^.rowBytes := docW + $8000;
  590.         gdPMap^^.pixelSize := 8;
  591.         gdPMap^^.cmpCount := 1;
  592.         gdPMap^^.cmpSize := 8;
  593.         
  594.         gdRect := sizeOfDoc;                                        { the bounding rectangle for gDevice, too. }
  595.     END;        { With fDrawingDevice }
  596.         
  597.         { Now unlock the gDevice handle since it is in the System Heap.  The system
  598.             can use it unlocked as well as locked so we try to help avoid fragmentation. }
  599.     HUnLock ( Handle(fDrawingDevice) );
  600.  
  601.     { Yow, that was rough.  Now we have a fully initialized gDevice offscreen with its
  602.         own colortable.  All color mapping should be done using that color table, and the
  603.         drawing we do to it should make the saved pictures save that color table too. 
  604.         Set to our new device so we OpenCPort with all new parameters. }
  605.     SetGDevice (fDrawingDevice);
  606.     
  607.     { After all of that, we have a gDevice which is complete.  It has the color table we want
  608.         associated with it, from the clut, it has the right portBits.baseAddr and the right
  609.         size.  It is complete, except that we can't draw into it using normal calls.  We thus
  610.         need to make a port that we can use.  We have set the gDevice to be the one we just
  611.         created, and when we OpenCPort we will get a copy of the fields we just set up in
  612.         our new gDevice.  The port is simply an interface into our gDevice for drawing.
  613.         Allocate a port record on the heap as a pointer.  (permanent memory useage).  We
  614.         allow the port to come out of temporary memory since it will blow up (most
  615.         unfriendly) if it cannot do it.  After it lives, though we want to check if we
  616.         have no more reserve, and if so we must bag this document. }
  617.     fDrawingPort := CGrafPtr( NewPtr (SizeOf (CGrafPort)) );    { address of C Port record. }
  618.     FailNil (fDrawingPort);                                    { didn't get it, means we die. }
  619.     
  620.     { Now the world is created, put memory allocation back to temporary, so that the
  621.         QD pieces can come out of temp memory as well.  No more permanent blocks are
  622.         allocated by us, except for the port, which cannot fail or we die.  }
  623.     dummy := PermAllocation (FALSE);
  624.  
  625.     OpenCPort (fDrawingPort);                                { make a new port offscreen. }
  626.     FailNoReserve;                                                    { Make reserve, die if we can't }
  627.         
  628.         { QuickDraw is most obnoxious about making a port that is bigger than the screen,
  629.             so we need to modify the visRgn to make it as big as our full page document.  It is
  630.             OK to change this ports visRgn since we own it offscreen.  This is in case we
  631.             are opening a document made on a different computer with a bigger screen. }
  632.     RectRgn(fDrawingPort^.visRgn, sizeOfDoc);
  633.  
  634.         { Go whap on the other pieces of the port record to set it up to be offscreen. }    
  635.     fDrawingPort^.portRect := sizeOfDoc;
  636.  
  637.     { OK, we have a nice new color port that is offscreen.  It has a fancy color table that
  638.         came from the clut that will be used for the owning window.  It is 8 bits deep,
  639.         has 256 colors in its color table and has a rect the size passed in.  It has no
  640.         pieces that are related to the main gDevice, so we shouldn't alter that by drawing
  641.         in this port.  }
  642.         
  643.         { Clear the error handler chain, we don't make any more dangerous requests. }
  644.     Success (fi);
  645.  
  646.  
  647.         { Set the memory allocation to what we started with. }
  648.     oldPerm := PermAllocation (oldPerm);
  649.  
  650.         { Now we have the offscreen PixMap, we need to initialize it to white. }
  651.     SetPort (GrafPtr(fDrawingPort));
  652.     EraseRect (sizeOfDoc);                { clear the bits. }
  653.  
  654.         { We are done drawing and stuff for now, so set the gDevice back to where it was. }
  655.     SetGDevice (currDevice);
  656.     SetPort (currPort);
  657. END;    { BuildOffWorld }
  658. *)
  659.  
  660. { This is a replacement proc that uses the new offscreen world calls from 32 bit QD }
  661. PROCEDURE TFracAppDocument.BuildOffWorld(sizeOfDoc:RECT); 
  662.  
  663. VAR        oldPerm                        :Boolean;
  664.             Dummy                        :Boolean;
  665.             docW,docH                    :LONGINT;
  666.             fi                                    :FailInfo;
  667.             currDev, auxDev        :GDHandle;
  668.             currPort                        :CGrafPtr;
  669.             erry                            :QDErr;
  670.             
  671.     PROCEDURE DeathDocument (error: OSErr; message:LONGINT);
  672.     
  673.     BEGIN
  674.         oldPerm := PermAllocation(oldPerm);
  675.         SetGWorld (currPort,  currDev); 
  676.     END;
  677.     
  678. BEGIN  (*myBuildOffWorld*)
  679.     GetGWorld(currPort,  currDev);
  680.     CatchFailures(fi, DeathDocument);            { any failures, must be cleaned up. }
  681.  
  682.     { Let's set up the size of the rectangle we are using for the document. }
  683.     docW := sizeOfDoc.right - sizeOfDoc.left;
  684.     docH := sizeOfDoc.bottom - sizeOfDoc.top;
  685.  
  686.     Erry := NewGWorld(fDrawingPort, 8, sizeOfDoc, gOurColors, NIL, GWorldFlags(0)); { see text for comments }
  687.     FailOSErr(Erry);                    { Not possible get out! }
  688.     
  689.     auxDev := GetGWorldDevice(fDrawingPort);        { Do I need this?     }
  690.     MakeITable(gOurColors, auxDev^^.gdITable, 3); { to save room         }
  691.     FailOSErr(QDError);                                           { sad, he says        }                
  692.  
  693.     SetGWorld (fDrawingPort,  NIL); 
  694.     
  695.     IF ( NOT LockPixels(fDrawingPort^.portPixMap) ) THEN
  696.        Debugger;
  697.        
  698.     EraseRect(FDrawingPort^.portRect);
  699.     
  700.     UnlockPixels (fDrawingPort^.portPixMap); 
  701.     
  702.     SetGWorld (currPort,  currDev); 
  703.  
  704.  
  705. END {myBuildOffWorld};
  706.  
  707.  
  708.     { Init for the FracAppDocument itself.  This sets up the Document object. }
  709.     { *************** Change here    ************** }
  710. PROCEDURE      TFracAppDocument.IFracAppDocument;
  711.  
  712. VAR        dummyTime:        LongInt;        { for picky compiler }
  713.     
  714. BEGIN
  715.     { Set up failure mechanism in case IDocument fails}
  716.     IDocument(kFileType, kSignature, kUsesDataFork,
  717.             NOT kUsesRsrcFork, NOT kDataOpen, NOT kRsrcOpen);
  718.     
  719.     { The document object is installed as a target object so that it will
  720.         get time to calculate the fractal.  Setting the time to 0 will get us calls
  721.         to DoIdle in the document. }
  722.     fIdleFreq := 0;
  723.     
  724.     { Set the time in our starting time variable in case we are still calculating.
  725.         Temp var is to make the picky compiler not get worried about the var
  726.         parameter.  This routine can't move memory anyway, but it won't allow
  727.         this use. }
  728.     GetDateTime (dummyTime);
  729.     fStartTime := dummyTime;
  730.     
  731.     fBigBuff := NIL;
  732.     fDrawingPort := NIL;                                    { set up in case we fail in here. }
  733.     fDrawingDevice := NIL;
  734. END;    { TFracAppDocument.IFracAppDocument }
  735.  
  736.  
  737.     { Does the work for a New operation, where we start with a new fractal
  738.         that doesn't have any stored data.  This is to set up the view with no
  739.         data and set up the fractal coordinates to the default.  It will use the size
  740.         of the  main screen to make a new document, and create the offscreen 
  741.         world to match.  If the global variable of gRealMin and gRealMax are both
  742.         nonzero, then we want to use the global state being passed us by the 
  743.         New Fractal handler.  This is for the zoom in. }
  744. PROCEDURE TFracAppDocument.DoInitialState;  OVERRIDE;
  745.     
  746. BEGIN
  747.     WITH  fFracHeader  DO BEGIN
  748.         { Start by filling in the fields that never change. }
  749.         fType := kSignature;            { creator of these documents. }
  750.         hdrId := INTEGER ('FA');        { ID of the file, different from other PICT files. }
  751.         version := 1;                        { version 1 files. 0 was old MandibleJug docs. }
  752.         
  753.         done := FALSE;                    { not done, starting brand new document. }
  754.     
  755.         { We start from scratch.  This is the standard set of coordinates to start
  756.             the default Mandelbrot set. 
  757.             Set up the coordinates to do, saving state in header vars. }
  758.         realMin := -2.5;  realMax := 1.5;
  759.         imagMin := -1.5;  imagMax := 1.5;
  760.         
  761.             { If we are supposed to do a zoom in, use those numbers instead. }
  762.         IF (gPageRec.RealMin <> 0) AND (gPageRec.RealMax <> 0)  THEN BEGIN
  763.             realMin := gPageRec.RealMin;        realMax :=gPageRec.RealMax;
  764.             imagMin := gPageRec.ImagMin;        imagMax := gPageRec.ImagMax;
  765.         END;
  766.         
  767.             { Set the fractal rectangle to be the full page size at 300 dpi. }
  768.         SetRect(calcRect, 0, 0, kRectRight, kRectBottom);
  769.  
  770.     END;        { With FracHeader }
  771.     
  772.         { Clear the state of the globals so any new documents will not be zoom in types. }
  773.     gPageRec.RealMin := 0;        gPageRec.RealMax := 0;
  774.     
  775.         { Build the initial state of the document offscreen gDevice & port }
  776.     BuildOffWorld (fFracHeader.calcRect);
  777.     
  778.         { Set up the rest of the constants that are used in the fractal, including
  779.             the deltas in each axis and the step constants for stepping through
  780.             each point in the fractal plane. }
  781.     SetUpConstants;
  782. END;        { TFracAppDocument.DoInitialState }
  783.  
  784.  
  785. PROCEDURE TFracAppDocument.DoMakeViews(forPrinting: BOOLEAN); OVERRIDE;
  786.  
  787. VAR        aFracAppView:    TFracAppView;
  788.  
  789. BEGIN
  790.     { Create a new view (failing if we can't), get a rectangle with
  791.         the appropriate extent, and initialize the view. }
  792.     New(aFracAppView);
  793.     FailNil(aFracAppView);
  794.  
  795.     { Initialize the view for use as a drawing environment. }
  796.     aFracAppView.IFracAppView (SELF, fFracHeader.calcRect);
  797.  
  798.     {save a reference to the view in a TFracAppDocument field, for use
  799.         by DoMakeWindows}
  800.     fFracAppView := aFracAppView;
  801. END;    { TFracAppDocument.DoMakeViews }
  802.  
  803.  
  804. PROCEDURE TFracAppDocument.DoMakeWindows; OVERRIDE;
  805.  
  806. VAR        aWindow:    TWindow;
  807.     
  808. BEGIN
  809.     { Gets window definition from resource file; the window is to have both horizontal 
  810.         and vertical scrollbars, and is to have my 'fFracAppView' installed in it;
  811.         NewSimpleWindow will exit via the failure mechanism if allocation fails. 
  812.         There is a palette associated with this window by Resource Id, so it will
  813.         automatically get used when the window is created. }
  814.     aWindow := NewSimpleWindow(kFracAppWindowID,
  815.                     kWantHScrollBar, kWantVScrollBar, SELF, fFracAppView);
  816.                     
  817.     aWindow.SimpleStagger(kStaggerAmount, kStaggerAmount, gStaggerCount);
  818. END;    { TFracAppDocument.DoMakeWindows }
  819.  
  820.  
  821.     { This routine will size the current image as it goes to the disk.  It won't actually
  822.         save any data or anything, but will merely watch the bytes go by keeping track
  823.         of how many go by.  The size is used by DoNeedDiskSpace. }
  824. PROCEDURE  PictSizer (dPointer: Ptr; nextHunk: Integer);
  825.  
  826. BEGIN
  827.     gPictSize := gPictSize + nextHunk;
  828. END;
  829.  
  830.  
  831.     { Routine to find out how much disk space will be required to save the data.  
  832.         This does not call the Inherited DoNeedDiskSpace since we don't support
  833.         printing info here.  The routine will replace the PutPicProc of the port
  834.         with our PictSizer routine.  When the picture is created here, no bytes 
  835.         will actually be allocated or saved, we will just watch it go by and 
  836.         save off the size in the global variable.  That value is returned
  837.         as the expected document size. }
  838. PROCEDURE TFracAppDocument.DoNeedDiskSpace(VAR dataForkBytes, 
  839.             rsrcForkBytes: LONGINT);   OVERRIDE;
  840.             
  841. VAR        picPort:            GrafPtr;
  842.             currDevice:        GDHandle;
  843.             currPort:            GrafPtr;
  844.             newGrafs:            CQDProcs;
  845.             oldProcs:            QDProcsPtr;        { bug in include files, CGrafPort has QDProcs *** }
  846.             
  847. BEGIN
  848.     { Create a picture Item itself, by opening the picture and doing the CopyBits
  849.         operation to the same port.  That picture will then be packed using the
  850.         normal packing operation of the Mac.  That block is then the data to be
  851.         written to the file. }
  852.         
  853. (*    currDevice := GetGDevice;                                { save off current one. }
  854.     GetPort (currPort);                                                                                        *)
  855.     GetGWorld(CGrafPtr(currPort),  currDevice);
  856.     
  857. (*    SetGDevice (fDrawingDevice);                            { set to ours for drawing in it. }   *)
  858.     SetGWorld (fDrawingPort,  NIL); 
  859.     picPort := GrafPtr (fDrawingPort);                    { the pointer to our port. }
  860.     SetPort (picPort);                                            { set there to do pict saving. }
  861.  
  862.         { Save the pointer to the current CGrafProcs }
  863.     oldProcs := thePort^.grafProcs;
  864.     
  865.         { Set our GrafProc record up to have the standard pieces. }
  866.     SetStdCProcs(newGrafs);
  867.     
  868.         { Change the port to use those GrafProcs instead. }
  869.     thePort^.grafProcs := @newGrafs;
  870.     
  871.         { We are in our offscreen port.  Change the GrafProc pointer for picture saving. }
  872.     newGrafs.putPicProc := @PictSizer;
  873.         
  874.         { Init the size of the pict we are going to save.  Start with picture header. }
  875.     gPictSize := SIZEOF (Picture);
  876.     
  877.         { The current gDevice is our offscreen device.  Now go ahead and open the picture 
  878.             and build it in RAM.  We would have done this by slices before, but the newer
  879.             systems have a patch for playing back pictures that minimize the RAM hit, so
  880.             we don't have to worry about the full screen CopyBits here. }
  881.     WITH  picPort^  DO BEGIN
  882.         gPictHandle := OpenPicture (portRect);
  883.         
  884.             IF ( NOT LockPixels(fDrawingPort^.portPixMap) ) THEN
  885.        Debugger;
  886.        
  887.                 { copy all of the image to itself, in an open picture it saves the bits. }
  888.             CopyBits (portBits, portBits, portRect, portRect, srcCopy, NIL);            
  889.     
  890.             UnlockPixels (fDrawingPort^.portPixMap); 
  891.  
  892.             
  893.         ClosePicture;                        { the picture is created, and packed. }
  894.     END;        { with picPort^ }
  895.  
  896.  
  897.         { Done saving the size of the picture itself.  Now set the GrafProcs back to normal. }
  898.     thePort^.grafProcs := oldProcs;
  899.     
  900.         { Dispose the pict handle, we didn't actually make anything there. }
  901.     KillPicture (gPictHandle);
  902.     gPictHandle := NIL;
  903.     
  904.     { Set the drawing device back where it belongs, in case of error, we get right device. }
  905. (*    SetGDevice (currDevice);                                    { set back to system for normal. }
  906.     SetPort (currPort);                                *)
  907.     SetGWorld (CGrafPtr(currPort),  currDevice); 
  908.  
  909.     { The picture has been sized.  Now add that in to the total size the file will use on
  910.         disk, include the header for the file, plus the number of bytes in actual PICT. }
  911.     dataForkBytes := dataForkBytes + gPictSize + kPICTHeaderSize;
  912. END;        { DoNeedDiskSpace }
  913.  
  914.  
  915.     { This routine will save the current image as it is created.  As the data requests
  916.         go by that data will be written to the file.  The data is being created by the
  917.         OpenPicture/CopyBits in DoWrite, this is the bottleneck for that operation.
  918.         Any errors found while doing this will make us skip any further requests
  919.         to write data to the disk.  No memory is allocated.   Communication with 
  920.         DoWrite is done through globals, since bottlenecks must be at the main
  921.         level.  The bottleneck must also keep track of how many bytes are written,
  922.         so that the header on the picture can be fixed up to be correct.  This must
  923.         be done to avoid creating bogus pictures.  The picSize field of the handle
  924.         must be updated continuously so that when the picture is done, the ClosePicture
  925.         can create a valid picture.  The check for the NIL handle is to handle the
  926.         problem of when the OpenPicture is called.  The proc gets called before 
  927.         the handle is valid.  Be very careful of these bottleneck things, it is 
  928.         easy to run into problems that are very hard to figure out.  QuickDraw
  929.         has no facilities to give you info when things go wrong so it makes it
  930.         a bit tougher. }
  931. PROCEDURE  PictWriter (dPointer: Ptr; nextHunk: Integer);
  932.  
  933. VAR    longHunk:    LongInt;
  934.  
  935. BEGIN
  936.     IF gPictError = noErr THEN  BEGIN
  937.         longHunk := nextHunk;
  938.         gPictError := FSWrite(gPictRefNum, longHunk, dPointer);
  939.         gPictSize := gPictSize + longHunk;
  940.         IF gPictHandle <> NIL THEN  gPictHandle^^.picSize := LoWord (gPictSize);
  941.     END;
  942. END;
  943.  
  944.  
  945.     { Write the data calculated into the document to the file.  This will make it a real
  946.         PICT file.  It writes the header first, then the PICT data.  This is so that it 
  947.         will still be a normal PICT file and can be used by other programs.  
  948.         The file will be saved using QuickDraw Bottlenecks for the PutPicProc.  
  949.         As the data requests go by, they will be written to the file, using the
  950.         PictWriter routine. }
  951. PROCEDURE TFracAppDocument.DoWrite(aRefNum: INTEGER; makingCopy: BOOLEAN);
  952.             OVERRIDE;
  953.  
  954. VAR        recSize:            LongInt;
  955.             fi:                     FailInfo;
  956.             picPort:            GrafPtr;
  957.             currDevice:        GDHandle;
  958.             currPort:            GrafPtr;
  959.             newGrafs:            CQDProcs;
  960.             oldProcs:            QDProcsPtr;        { bug in include files, CGrafPort has QDProcs *** }
  961.  
  962.     PROCEDURE DeathWrite (error: OSErr; message: LONGINT);
  963.     BEGIN
  964.         IF gPictHandle <> NIL THEN      KillPicture (gPictHandle);
  965.         gPictHandle := NIL;
  966.  
  967.         thePort^.grafProcs := oldProcs;
  968.         SetGDevice (currDevice);                { set back to system for normal. }
  969.         SetPort (currPort);
  970.     END;
  971.     
  972. BEGIN
  973.         { We have legit data in our document, set the mark in the file to be at the front. }
  974.     FailOSErr ( SetFPos (aRefNum, fsFromStart, 0) );
  975.  
  976.         { Write the FracHeader to the file, it includes the pertinent details about
  977.             the fractal including the global state for it. }
  978.     recSize := SIZEOF (FracRecord);                           { our header on fractal files. }
  979.     FailOSErr ( FSWrite (aRefNum, recSize, @fFracHeader) );
  980.  
  981.         { Now we need to write the picture data itself out to the file, after we set the
  982.             mark to be after the entire header.  Make sure the file is that big before we do it. 
  983.             Included in this set is the header of the picture itself, the 10 bytes that
  984.             include the rectangle.  Those bytes will be updated after the picture is
  985.             written. }
  986.     FailOSErr ( SetEOF (aRefNum, kPICTHeaderSize+SIZEOF (Picture) ) );
  987.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize+SIZEOF (Picture) ) );
  988.     
  989.         { The file is all set up to go.  We now want to replace the QuickDraw bottleneck
  990.             and create the actual Picture data. }
  991. (*    currDevice := GetGDevice;                                { save off current one. }
  992.     GetPort (currPort);                                    *)
  993.     GetGWorld(CGrafPtr(currPort),  currDevice);
  994.     
  995.         { If the write of the picture header fails, we want to dispose the handle allocated. }
  996.     CatchFailures(fi, DeathWrite);
  997.  
  998.         { Move over to the offscreen port/device. }
  999. (*    SetGDevice (fDrawingDevice);                            { set to ours for drawing in it. }      *)
  1000.     SetGWorld (fDrawingPort,  NIL); 
  1001.     picPort := GrafPtr (fDrawingPort);                    { the pointer to our port. }
  1002.     SetPort (picPort);                                            { set there to do pict saving. }
  1003.  
  1004.         { Save the pointer to the current CGrafProcs }
  1005.     oldProcs := thePort^.grafProcs;
  1006.     
  1007.         { Set our GrafProc record up to have the standard pieces. }
  1008.     SetStdCProcs(newGrafs);
  1009.     
  1010.         { Change the port to use those GrafProcs instead. }
  1011.     thePort^.grafProcs := @newGrafs;
  1012.     
  1013.         { We are in our offscreen port.  Change the GrafProc pointer for picture saving. }
  1014.     newGrafs.putPicProc := @PictWriter;
  1015.         
  1016.         { Tell PictWriter what file to write to, and start the pic size including the
  1017.             picture header.  Start all the pieces off the right way. }
  1018.     gPictRefNum := aRefNum;
  1019.     gPictSize := SIZEOF(Picture);
  1020.     gPictError := noErr;
  1021.     gPictHandle := NIL;
  1022.  
  1023.         { Actually open the picture and do the CopyBits in order to process the picture.
  1024.             The data will be written by PictWriter as it is called by QuickDraw. }
  1025.     WITH  picPort^  DO BEGIN
  1026.         gPictHandle := OpenPicture (portRect);
  1027.             ClipRect(portRect);            { Make it a happier picture. }
  1028.  
  1029.     IF ( NOT LockPixels(fDrawingPort^.portPixMap) ) THEN
  1030.        Debugger;
  1031.        
  1032.                 { copy all of the image to itself, in an open picture it saves the bits. }
  1033.             CopyBits (portBits, portBits, portRect, portRect, srcCopy, NIL);            
  1034.             
  1035.     UnlockPixels (fDrawingPort^.portPixMap); 
  1036.     
  1037.         ClosePicture;                        { the picture is created, and packed. }
  1038.     END;        { with picPort^ }
  1039.     
  1040.         { Now check for errors during the write operation.  The gPictError field will be
  1041.             nonzero if we failed during the operation. }
  1042.     FailOSErr (gPictError);
  1043.         
  1044.         { Move back to front of file and write the valid picture info to file. }
  1045.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize) );
  1046.     recSize := SIZEOF(Picture);
  1047.     FailOSErr (FSWrite(aRefNum, recSize, Ptr(gPictHandle^)));
  1048.  
  1049.         { Done saving the data of the picture itself.  Now set the GrafProcs back to normal. }
  1050.     thePort^.grafProcs := oldProcs;
  1051.     
  1052.         { Dispose the pict handle, we didn't actually make anything there. }
  1053.     KillPicture (gPictHandle);
  1054.     gPictHandle := NIL;
  1055.  
  1056.         { Set the drawing device back where it belongs, in case of error, we get right device. }
  1057. (*    SetGDevice (currDevice);                                    { set back to system for normal. }
  1058.     SetPort (currPort);                                *)
  1059.     SetGWorld (CGrafPtr(currPort),  currDevice); 
  1060.  
  1061.         { If we lived through it, clear error handler. }
  1062.     Success (fi);
  1063. END;            { TFracAppDocument.DoWrite }
  1064.  
  1065.  
  1066.     { The bottleneck routine to read the picture from the disk.  This will read the
  1067.         data required, and pass it along to the unpacker.  This makes it possible to
  1068.         avoid using any RAM for the actual reading part, as it is being played back
  1069.         into the offscreen device.  Error handling is somewhat tricky, since we 
  1070.         need to force the picture to finish, and there isn't a really good way to 
  1071.         do this.  The desired attempt here is to pass back a picture is finished 
  1072.         opcode ($00FF) so we can get back to our code to handle the error.  This is 
  1073.         better than no error recovery, but is not guaranteed to work. }
  1074. PROCEDURE  PictReader (dPointer: Ptr; nextHunk: Integer);
  1075.  
  1076. VAR    longHunk:    LongInt;
  1077.         I:                Integer;
  1078.  
  1079. BEGIN
  1080.     IF gPictError = noErr THEN  BEGIN
  1081.         longHunk := nextHunk;
  1082.         gPictError := FSRead(gPictRefNum, longHunk, dPointer);
  1083.     END
  1084.     ELSE            { handle the error situation by passing back $00FF as the data.? }
  1085.         FOR I := 1 to nextHunk  DO BEGIN
  1086.             IF ODD (I) THEN  dPointer^ := $00
  1087.             ELSE  dPointer^ := $FF;
  1088.             dPointer := PTR (ORD4(dPointer) + 1);
  1089.         END;
  1090. END;
  1091.  
  1092.  
  1093.     { Routine to read the data from the data fork of the file into our document so it
  1094.         can be displayed.  The quickdraw bottleneck will be replaced with the
  1095.         PictReader routine, making it read the data from the disk as the picture
  1096.         requests more data.  This obviates the need for an extra handle that is
  1097.         used to play back the picture.  This is done since that extra handle can
  1098.         be on the order of 100K, memory we may not have available. }
  1099. PROCEDURE TFracAppDocument.DoRead(aRefNum: INTEGER; rsrcExists,
  1100.                                                   forPrinting: BOOLEAN);  OVERRIDE;
  1101.  
  1102. VAR        recSize:            LongInt;
  1103.             fi:                     FailInfo;
  1104.             currDevice:        GDHandle;
  1105.             currPort:            GrafPtr;
  1106.             newGrafs:            CQDProcs;
  1107.             oldProcs:            QDProcsPtr;        { bug in include files, CGrafPort has QDProcs *** }
  1108.  
  1109.     PROCEDURE DeathRead (error: OSErr; message: LONGINT);
  1110.     BEGIN
  1111.         IF gPictHandle <> NIL THEN  KillPicture (gPictHandle);
  1112.         gPictHandle := NIL;
  1113.     END;
  1114.     
  1115. BEGIN
  1116.         { The file is open already, we just have to read the data out of it.  The first thing
  1117.             to read is the header we use to describe a fractal.  If we get an error
  1118.             here we need to split since we should always have at least a header.  The fractal
  1119.             header is the global state for the document.  We just read it into the record
  1120.             and use it from there. }
  1121.     FailOSErr ( SetFPos (aRefNum, fsFromStart, 0) );    { starts at first byte of file. }
  1122.     recSize := SIZEOF (FracRecord);                                   { size of header on fractal files. }
  1123.     FailOSErr ( FSRead (aRefNum, recSize, @fFracHeader) );
  1124.     
  1125.         { We have the header for the PICT file.  Now we need to be sure that it is a fractal
  1126.             document, and not something we can't use.  Check the header to be sure, and if
  1127.             not right, error out with a good alert message (using a standard MacApp errcode). }
  1128.     IF fFracHeader.fType <> kSignature  THEN  FailOSErr (errNotMyType);
  1129.  
  1130.         { We have the data from the header, go ahead and set up an offscreen world for this
  1131.             document, using the header rectangle. }
  1132.     BuildOffWorld (fFracHeader.calcRect);
  1133.     
  1134.         { Make sure the file position is right at the start of the picture in the file. }
  1135.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize) );
  1136.  
  1137.         { Allocate a small handle that will be used as the Pict handle for drawing from
  1138.             the disk.  This is just the picture header. }
  1139.     gPictHandle := PicHandle (NewHandle (SIZEOF(Picture)));
  1140.     FailNil (gPictHandle);
  1141.     
  1142.         { If the read of the picture header fails, we want to dispose the handle allocated. }
  1143.     CatchFailures(fi, DeathRead);
  1144.  
  1145.         { Tell PictReader what file to read from. }
  1146.     gPictRefNum := aRefNum;
  1147.     gPictError := noErr;
  1148.     
  1149.         { Now fill in the picture header itself, using the data from the disk. }
  1150.     recSize := SIZEOF(Picture);
  1151.     gPictError := FSRead(aRefNum, recSize, Ptr (gPictHandle^));
  1152.     FailOSErr (gPictError);
  1153.     
  1154.         { That is the only call we can't recover from immediately, the rest of the
  1155.             routine is not easy to recover from, so we won't go through DeathRead. }
  1156.     Success (fi);
  1157.     
  1158.         { The file position is right at the beginning of the picture data, so we can just
  1159.             install the bottleneck and call DrawPicture to fill our offscreen gDevice
  1160.             with the data that was saved.  Set to that port and gDevice for playback. }
  1161. (*    currDevice := GetGDevice;                        { save current to get back. }
  1162.     GetPort (currPort);                                                                                        *)
  1163.     GetGWorld(CGrafPtr(currPort),  currDevice);
  1164.  
  1165. (*    SetGDevice (fDrawingDevice);
  1166.     SetPort (GrafPtr(fDrawingPort));                                                                *)
  1167.     SetGWorld (fDrawingPort,  NIL); 
  1168.  
  1169.         { Save the pointer to the current CGrafProcs }
  1170.     oldProcs := thePort^.grafProcs;
  1171.     
  1172.         { Set our GrafProc record up to have the standard pieces. }
  1173.     SetStdCProcs(newGrafs);
  1174.     
  1175.         { Change the port to use those GrafProcs instead. }
  1176.     thePort^.grafProcs := @newGrafs;
  1177.     
  1178.         { We are in our offscreen port.  Change the GrafProc pointer for picture reading. }
  1179.     newGrafs.getPicProc := @PictReader;
  1180.         
  1181.         { Now we have the buffer and the offscreen port.  We can draw the picture that
  1182.             will be read out of the file into this port in order to init the port for later use in
  1183.             updating the window.  We are already set to draw in the offscreen port.  Do the
  1184.             DrawPicture to have PictReader read the data out of the file while it is being
  1185.             played into the offscreen Port. }
  1186.     IF ( NOT LockPixels(fDrawingPort^.portPixMap) ) THEN
  1187.        Debugger;
  1188.  
  1189.     DrawPicture(gPictHandle, gPictHandle^^.picFrame);
  1190.     
  1191.     UnlockPixels (fDrawingPort^.portPixMap); 
  1192.     
  1193.         { Done reading the data of the picture itself.  Now set the GrafProcs back to normal. }
  1194.     thePort^.grafProcs := oldProcs;
  1195.     
  1196.         { Bag the handle we made for playing back the picture. }
  1197.     KillPicture (gPictHandle);
  1198.     gPictHandle := NIL;
  1199.  
  1200.         { Set back to the normal drawing environment. }
  1201.     SetGDevice (currDevice);
  1202.     SetPort (currPort);
  1203.         
  1204.         { If we had an error while reading the data, we must error out. }
  1205.     FailOSErr (gPictError);
  1206. END;        { TFracAppDocument.DoRead }
  1207.  
  1208.  
  1209.     { This is typically used in a Revert case which is not really meaningful here, but
  1210.         the structure is the same so we use it anyway.  Frees the data associated with
  1211.         a document, that is strictly program data, not MacApp data. }
  1212. PROCEDURE TFracAppDocument.FreeData; OVERRIDE;
  1213.  
  1214. BEGIN
  1215.         { Kill the bits for the offscreen bitMap if they were allocated. }
  1216.     IF fBigBuff <> NIL  THEN  DisposPtr (fBigBuff);
  1217.         { Close the port: remove from portList, kill visRgn and clipRgn, kill the penPixPat
  1218.             and fill PixPat and back PixPat, kill PixMap handle, kill grafVars handle. }
  1219.     IF fDrawingPort <> NIL  THEN  BEGIN
  1220.         CloseCPort (fDrawingPort);
  1221.         DisposPtr (Ptr (fDrawingPort) );
  1222.     END;
  1223.         { DisposGDevice does: kills the ITable, kills Cursor expanded data and mask if
  1224.             nonzero, calls DisposPixMap if gdPMap is nonzero, then disposes the gDevice
  1225.             handle itself.  DisposPixMap kills the colorTable and the pixMap record. }
  1226.     IF fDrawingDevice <> NIL  THEN DisposGDevice (fDrawingDevice);
  1227. END;    { TFracAppDocument.FreeData }
  1228.  
  1229.             
  1230.     { Free method for the documents themselves.  We need to override so that we
  1231.         can throw away the data object that was read in from the disk if it exists. 
  1232.         Also chuck the gDevice and port used for the document data. }
  1233. PROCEDURE TFracAppDocument.Free; OVERRIDE;
  1234.  
  1235. BEGIN
  1236.     FreeData;
  1237.     
  1238.     INHERITED Free;
  1239. END;    { TFracAppDocument.Free }
  1240.  
  1241.  
  1242.     { This handles the idling of the document, basically calling the calc routine
  1243.         to see what needs to happen.  If the doc is done, then close it and start
  1244.         a new one up. (probably not safe, and need to pass it along to app instead.) }
  1245. FUNCTION TFracAppDocument.DoIdle(phase: IdlePhase):  BOOLEAN; OVERRIDE;
  1246.     
  1247.     VAR            killIt:            Boolean;
  1248.     
  1249. BEGIN
  1250.     killIt := CalcCity;
  1251.     IF killIt THEN BEGIN
  1252.         Close;
  1253.         IF (gPageRec.currentV <> 0) and (gPageRec.currentH <> 0) THEN 
  1254.             gApplication.OpenNew(cNew);
  1255.     END;
  1256. END;        { TFracAppDocument.DoIdle }
  1257.  
  1258.  
  1259.     { The procedure to do the idle time processing in the document.  This will do the    
  1260.         entire fractal calculation so as to be able to do it in the background.  It
  1261.         does it one pixel at a time to avoid any hit on performance for the
  1262.         foreground application.  This is called in response to the DoIdle for the
  1263.         application.  The fIdlePriority is not set for this method, so it won't get
  1264.         time except when the application calls specifically.  It is done this way
  1265.         since otherwise the target chain would need to have each document in
  1266.         the list, which is not desireable for other event handling.  Notably the
  1267.         time keeper in here is not too accurate.  Each pixel takes less than a 
  1268.         tick to calculate, making it a bit tougher.  A way to make it more 
  1269.         accurate would be to figure out the maximum time for a full black
  1270.         document, and divide by the number of pixels in the screen and the
  1271.         number of loops.  That number (in microseconds) could be added each
  1272.         time through the calculation loop to give a more accurate timestamp.
  1273.         This would be wrong if the clock changes, so perhaps it should use the
  1274.         low memory TimeDBRA value as units instead.
  1275.         This is left as an exercise for the reader. 
  1276.         The idling of the target chain makes the document get a DoIdle call 
  1277.         during idling, but only the top document gets the call.  If we install the
  1278.         doc in the coHandler chain, we end up getting two calls for each idle,
  1279.         which may be OK. If the doc is in the background, it ends up not getting
  1280.         any calls at all, since there is no target chain when it is in the background,
  1281.         under the assumption that a DA is in front. }
  1282. FUNCTION  TFracAppDocument.CalcCity: BOOLEAN;
  1283.  
  1284. CONST    M                = 100;                    { this decides what 'infinity' is.  If value less than this, loop. }
  1285.             K                = kNumColors;        { number of colors to choose from. Also iterations times.  This
  1286.                                                             is 195 to match the clut created for it. }
  1287.             BlackPen    = 255;                    { entry in our modified color table for black. }
  1288.  
  1289. VAR        currTime:            LongInt;        { temp var for time check. }
  1290.     
  1291.             x,y,x1,y1:            Extended;        { for interim values of current point. }
  1292.             Po,Qo:                Extended;
  1293.     
  1294.             kol:                    Integer;        { color we are currently on. }
  1295.             r:                        Extended;        { 'distance' from root. }
  1296.             currDevice:        GDHandle;        { current gDevice handle, so we can get back there. }
  1297.             currPort:            GrafPtr;
  1298.             drawRect:            Rect;            { for updating the screen as we calculate. }
  1299.             doUpdate:            Boolean;        { true if we finish a row on this calculation. }
  1300.             
  1301.             deltaReal,
  1302.             deltaImag:        Extended;        { rectangle size when making new multiple page doc. }
  1303.             Hpage,
  1304.             Vpage:                Str255;
  1305.             PageH:                Handle;            { for changing the resource. }
  1306.             
  1307.  
  1308. BEGIN
  1309.         { Don't close it now if we didn't before. }
  1310.     CalcCity := FALSE;
  1311.  
  1312.         { Calculate the fractal as we go.  Do next pixel here, based on the state saved
  1313.             in the document object.  When done, the variables are updated to go to the
  1314.             next location to do.  It sets the pixel in the offscreen port to be whatever
  1315.             we calculate it to be.  The buffer will be copied to the screen at update time. 
  1316.             The global state is saved in the FracHeader record in the document object.
  1317.             That state is saved across the use of a document, so it will always be right. }
  1318.     
  1319.         { If we are done, or not started, we can split. }
  1320.     IF  fFracHeader.done THEN  Exit(CalcCity);
  1321.     
  1322.     GetGWorld(CGrafPtr(currPort),  currDevice);
  1323. (*    currDevice := GetGDevice;                    { save off current one. }
  1324.     GetPort (currPort);                                                                                *)
  1325.     
  1326. (*    SetGDevice (fDrawingDevice);                { set to ours for drawing in it. }
  1327.     SetPort (GrafPtr(fDrawingPort));        { draw in offscreen guy. }             ******* *)
  1328.     SetGWorld (fDrawingPort,  NIL); 
  1329.  
  1330.     { Now do the calculation to determine the color of the pixel at the
  1331.         current location.  Uses the header saved state. }
  1332.  
  1333.     With  fFracHeader  DO BEGIN
  1334. (*        x := realMin + curCol * deltaP;                    { Use these for a Julia set calculation. } 
  1335.         y := imagMin + curRow * deltaQ;
  1336.         kol := 0;
  1337.         Po := -0.39054;
  1338.         Qo := 0.58679;
  1339. *)        
  1340.         Po := realMin + curRow * deltaP;                { next starting point  }
  1341.         Qo := imagMin + curCol * deltaQ;
  1342.         kol := 0;
  1343.         x := 0;                                                        { Mandel set starts with 0 always. }
  1344.         y := 0;                                                        { For Julia set you start with previous number. }
  1345.     END;        { With }
  1346.     
  1347.     REPEAT
  1348.         { the following is for y = X^2 + C for imaginary numbers.  
  1349.         pt1 = x + yi,  C = Po + Qoi,  in pt2 := pt1^2 + C  }
  1350.         
  1351.         x1 := x*x - y*y + Po;
  1352.         y1 :=2*x*y + Qo;
  1353.         
  1354.         kol := kol + 1;
  1355.         x := x1;  y := y1;
  1356.         
  1357.         r := x1*x1 + y1*y1;
  1358.     UNTIL  (r > M) OR (kol > K);                { Until 'distance' > our infinity, or out of colors. }
  1359.     
  1360.         { It's only legal to set the foreground color directly here because we are
  1361.             setting it in the offscreen port.  This should not be done for ports that are
  1362.             not completely owned by the application, like those going to the screen. }
  1363.     IF kol <= K  THEN                                { r must be > M. }
  1364.         fDrawingPort^.fgColor := kol            { set the color }
  1365.     ELSE                                                    { must be kol > K, ran out of colors. }
  1366.         fDrawingPort^.fgColor := BlackPen;
  1367.     
  1368.     IF ( NOT LockPixels(fDrawingPort^.portPixMap) ) THEN
  1369.        Debugger;
  1370.        
  1371.         { Move to the pixel we calculated for, then draw the pixel in right color.  This 
  1372.             could be done by setting the bytes in pixel map directly, since we own the
  1373.             PixMap and the buffer. }
  1374.     MoveTo (fFracHeader.curCol, fFracHeader.curRow);
  1375.     Line (0,0);                                           { draw that 'pixel' in the right color }
  1376.     
  1377.     UnlockPixels (fDrawingPort^.portPixMap); 
  1378.     
  1379.         { Done drawing in offscreen port, set back to regular, so the possible AutoSave
  1380.             will work properly. }
  1381. (*    SetGDevice (currDevice);
  1382.     SetPort (currPort);                        *)
  1383.     SetGWorld (CGrafPtr(currPort),  currDevice); 
  1384.  
  1385.         { Now we have changed another point in the document.  We need to mark it as
  1386.             changed so we can save the document. }
  1387.     fChangeCount := fChangeCount + 1;
  1388.  
  1389.         { up the counters to the next pixel location to do. }
  1390.     WITH  fFracHeader  DO BEGIN
  1391.         curCol := curCol + 1;                                    { up the column count. }
  1392.         doUpdate := FALSE;                                    { Assume we don't need update. }
  1393.         IF curCol >= plotWidth  THEN  BEGIN            { did we run off end of row? }
  1394.             doUpdate := TRUE;                                    { done with row, force update. }
  1395.             curCol := 0;                                            { start on the next row. }
  1396.             curRow := curRow + 1;                            { and up the counter of the next row to do. }
  1397.         END;  { start at next row. }
  1398.     END;  { with  fFracHeader }
  1399.  
  1400.         { Check if we are done, and if so, set the flag to stop calculations.  Set the
  1401.             elapsed time counter in the header. }
  1402.     IF fFracHeader.curRow >= fFracHeader.plotHeight  THEN  BEGIN
  1403.         fFracHeader.done := TRUE;
  1404.         GetDateTime(currTime);
  1405.         fFracHeader.elapsedTime := currTime - fStartTime;
  1406.         
  1407.             { See if we are doing multipage operation, and if so, save the document, close the
  1408.                 window and start a new one. }
  1409.         IF (gPageRec.currentH <> 0) AND (gPageRec.currentV <> 0)  THEN  BEGIN
  1410.                 { Now set the document title to be a derivative of the current position in
  1411.                     the set of documents making a multiple page setup.  A document method. }
  1412.             NumToString(gPageRec.currentH, HPage);
  1413.             NumToString (gPageRec.currentV, VPage);
  1414.             SetTitle (ConCat ('Set- ', HPage, ',' , VPage));
  1415.             FailOSErr (GetVol(NIL, fVolRefNum));
  1416.             
  1417.                 { Save the document with no questions asked, using the name of the document that
  1418.                     was set before.  If we have a problem with the file, an alert will come up freezing
  1419.                     the operation for user input, but that is OK.  We at least try to be automatic, but
  1420.                     if something freaks out it is OK to pause. }
  1421.             Save(cSave, {askForFilename:} FALSE, {makingCopy:} FALSE);
  1422.             
  1423.                 { Calculate the new subset to be done, based on the old set and where we are in the
  1424.                     multipage operation.   Start with the current document rectangle. }
  1425.             WITH  fFracHeader DO BEGIN
  1426.                 gPageRec.RealMin := realMin;
  1427.                 gPageRec.ImagMin := imagMin;
  1428.                 gPageRec.RealMax := realMax;
  1429.                 gPageRec.ImagMax := imagMax;
  1430.             END;
  1431.  
  1432.                 { Get current size of this document. }
  1433.             deltaReal := gPageRec.RealMax-gPageRec.RealMin;
  1434.             deltaImag := gPageRec.ImagMax-gPageRec.ImagMin;
  1435.             
  1436.                 { Now make the new rectangle based on where we are in the set of multipage
  1437.                     documents.  See if we are over the top horizontally, and if so, we update
  1438.                     the vertical too. }
  1439.             WITH  gPageRec  DO
  1440.                 IF currentH = maxH  THEN 
  1441.                     BEGIN
  1442.                         currentH := 1;
  1443.                         currentV := currentV + 1;
  1444.                         
  1445.                         ImagMin := ImagMin-((maxH-1) * deltaImag);
  1446.                         ImagMax := ImagMin + deltaImag;
  1447.                         
  1448.                             { Move down a page vertically, based on old position. }
  1449.                         RealMin := RealMax;
  1450.                         RealMax := RealMin + deltaReal;
  1451.                     END
  1452.                 ELSE                      { We just move one page to the right. }
  1453.                     BEGIN
  1454.                         currentH := currentH + 1;
  1455.                         
  1456.                         ImagMin := ImagMax;
  1457.                         ImagMax := ImagMin + deltaImag;
  1458.                     END;
  1459.                 
  1460.                 { Return the boolean that tells the DoIdle to close this document.  We can't
  1461.                     close here, because we are running out of the document method.  Have it
  1462.                     closed when we return from here. }
  1463.             CalcCity := TRUE;
  1464.                 
  1465.                 { A little funky, open a new document, it will use the global vars to make the right
  1466.                     fractal subset.  If we are off the end of the maximum number of pages vertically,
  1467.                     we  are done, so we can just mark it as done and skip it. }
  1468.             WITH gPageRec  DO 
  1469.                 IF currentV > maxV THEN
  1470.                     BEGIN
  1471.                         RealMin := 0;            RealMax := 0;
  1472.                         currentV := 0;        currentH := 0;
  1473.                     END;
  1474.                 
  1475.                 { Now we have the page count desired, save it off in the 'page' resource.  This is to save
  1476.                     the current state, so if we start up again, we know where to begin.  }
  1477.             pageH := GetResource('page', kPageId);
  1478.             FailNil (pageH);
  1479.             PageRecPtr(pageH^)^ := gPageRec;
  1480.             
  1481.                 { The resource has changed, mark it as such so it will get saved on a Quit.  This
  1482.                     saves the state information that we are starting afresh.  When a document is
  1483.                     finished, this is changed again to start the next document. }
  1484.             ChangedResource(pageH);
  1485.         END;    { doing multipage. }
  1486.     END;    { Done with document }
  1487.     
  1488.         { If we finished a row, update that row to the screen. }
  1489.     IF doUpdate  THEN
  1490.         WITH fFracHeader  DO  BEGIN
  1491.             SetRect (drawRect, calcRect.left, curRow-1, calcRect.right, curRow);
  1492.             fFracAppView.InvalidRect (drawRect);
  1493.         END;
  1494.  
  1495. END;        { TFracAppDocument.DoIdle } 
  1496.  
  1497.  
  1498. {-------------------------------  View  -------------------------------}
  1499.  
  1500.     { Initialize the view, basically set up the view object and clear the selection. }
  1501. PROCEDURE     TFracAppView.IFracAppView (itsDocument: TFracAppDocument;
  1502.         sizeOfView: Rect);
  1503. VAR        aHandler:        TStdPrintHandler;
  1504.             bigRect:        VRect;
  1505.             
  1506. BEGIN
  1507.     fSelectionRect := gZeroRect;                    { no selection to start with. }
  1508.     fFracAppDocument := itsDocument;        { save off parent document for convenience. }
  1509.     RectToVRect (sizeOfView, bigRect);
  1510.  
  1511.         { This view will be the full size of the screen since we have an offscreen
  1512.             bitMap as the view.  This will be clipped to fit the frame of the window.
  1513.             There is no parent view, and the horizontal and vertical are fixed.  The
  1514.             selection is to be shown, and is initially off. }
  1515.     IView(itsDocument, NIL, bigRect.topLeft, bigRect.botRight, sizeFixed, sizeFixed);
  1516.       
  1517.         { Printing handler as standard print handler will get a color port back
  1518.             from the driver.  The pixels are square, and the dimensions are fixed. }
  1519.     New(aHandler);
  1520.     FailNIL(aHandler);
  1521.     aHandler.IStdPrintHandler(fFracAppDocument, SELF, TRUE, TRUE, TRUE);
  1522.     
  1523. END;        { TFracAppView.IFracAppView }
  1524.  
  1525.  
  1526.     { Our routine to do the drawing of the fractal.  This is the display routine
  1527.         to take the data out of the offscreen buffer and whip it up to the window,
  1528.         as the current view. The fractal is full screen size, clips without
  1529.         scaling into the window. }
  1530. PROCEDURE TFracAppView.Draw(area: Rect); OVERRIDE;    
  1531.  
  1532. BEGIN        
  1533.     IF ( NOT LockPixels( CGrafPtr(fFracAppDocument.fDrawingPort)^.portPixMap) ) THEN
  1534.        Debugger;
  1535.        
  1536.         { Copy the bits to the screen, allowing CopyBits to sort out the colors.  
  1537.             A little slower for updates, but reasonable. }
  1538.     CopyBits ( GrafPtr(fFracAppDocument.fDrawingPort)^.portBits,
  1539.             thePort^.portBits, area, area, srcCopy, Nil);
  1540.             
  1541.     UnlockPixels (CGrafPtr(fFracAppDocument.fDrawingPort)^.portPixMap); 
  1542.     
  1543. END;        { TFracAppView.Draw }
  1544.  
  1545.  
  1546.     { Handle the menu choice for New Fractal out of the Fractal Menu.  This makes a new
  1547.         Fractal based on the current selection.  It does it by calling on the application
  1548.         object to make a new document.  The communication to the DoInitialState is
  1549.         through the global variables. }
  1550. FUNCTION TFracAppView.DoMenuCommand(aCmdNumber: CmdNumber): TCommand;  OVERRIDE;
  1551.  
  1552. VAR        oldDeltaP,
  1553.             oldDeltaQ,
  1554.             oldRealMin,
  1555.             oldImagMin:    Extended;                    { for figuring new fractal area. }
  1556.             pageDialog:        DialogPtr;                { input on multiple pages. }
  1557.             itemHit:            Integer;                    { for ModalDialog. }
  1558.             horPages:        LongInt;
  1559.             verPages:        LongInt;                    { after the dialog has been run. }
  1560.             kind:                Integer;
  1561.             r:                        Rect;
  1562.             itemH:                Handle;                    { handle to edittext item in dialog. }
  1563.             edString:            Str255;                    { Text out of EditText item. }
  1564.             pageH:                Handle;                    { Handle to 'page' resource. }
  1565.  
  1566. BEGIN
  1567.     { Assume that we have no command to return, since none of our commands currently
  1568.         change the document. }
  1569.     DoMenuCommand := gNoChanges;            { no command object returned. }
  1570.  
  1571.     { Case off on the various menus.  Currently we have the new fractal item.
  1572.         Any out of that list are handled by Mr. MacApp and we pass it on. }
  1573.     CASE  aCmdNumber  OF
  1574.     
  1575.         kNewFractal:
  1576.             { If the option chosen was the New Fractal item, then we need to start 
  1577.                 up a fresh one based on the selection rectangle.  This new fractal is 
  1578.                 based on parts of the old one, since it is
  1579.                 a zoom in operation.  We make a new document/window/view
  1580.                 as if it were a New operation.  We then change the fields we need to in
  1581.                 that document to make it start calculating based on the selection from
  1582.                 the current view. }
  1583.             
  1584.             BEGIN
  1585.                 { Make a new document and initialize it to the base state.  If we fail in 
  1586.                     opening it, we won't return here, the failure handler will kill it.  We
  1587.                     have nothing else to dispose of, so we don't make a CatchSignals here. 
  1588.                     This will come out of permanent memory.  The aCmdNumber is so that
  1589.                     the new document knows it came from a zoom in operation.  Since this
  1590.                     is somewhat funky, we communicate to the other part of the program
  1591.                     with the global variables.  If nonzero, the code that makes a
  1592.                     new document will know to use these numbers in order to do the zoom. 
  1593.                     This is less than completely desireable, but there are no good places
  1594.                     to override in order to get both the selection rectangle and the new
  1595.                     document objects. }
  1596.     
  1597.                     { The basic fractal has been set up.  We now need to change the calculation
  1598.                         area based on the current selection, in order to effect the zoom in. } 
  1599.                 WITH  fFracAppDocument.fFracHeader DO BEGIN
  1600.                     oldDeltaP := deltaP;                            { from SELF, the old document. }
  1601.                     oldDeltaQ := deltaQ;
  1602.                     oldRealMin := realMin;
  1603.                     oldImagMin := imagMin;
  1604.                 END;
  1605.  
  1606.                 { calculate new min/max for real and imaginary parts based on how far
  1607.                     into the old fractal plane we were.  This is an extended calculation
  1608.                     since our plane is in extendeds.  We get the new locations of min and
  1609.                     max, and save them off.  We reset the deltaP or Q with SetUpConstants,
  1610.                     in order to force a 1:1 ratio, but we need all sides to determine
  1611.                     which one to force.  }
  1612.                 WITH gPageRec DO BEGIN
  1613.                     RealMin := oldRealMin + oldDeltaP * fSelectionRect.top;
  1614.                     ImagMin := oldImagMin + oldDeltaQ * fSelectionRect.left;
  1615.                     RealMax := oldRealMin + oldDeltaP * fSelectionRect.bottom;
  1616.                     ImagMax := oldImagMin + oldDeltaQ * fSelectionRect.right;
  1617.                 END;
  1618.  
  1619.                 gApplication.OpenNew (aCmdNumber);
  1620.             END;
  1621.             
  1622.         kNewMultipage:
  1623.                 { When they choose the MultiPage option, we have to put up the dialog to find
  1624.                     out what they want to do, get the values back, store them into the 'page'
  1625.                     resource, and start up the first document based on the current selection
  1626.                     and the number of pages to do. }
  1627.                     
  1628.             BEGIN
  1629.             
  1630.                     { Run the dialog to get the number of pages desired. }
  1631.                 pageDialog := GetNewDialog(kMultiDialog, Nil, Pointer(-1));
  1632.                 ModalDialog(Nil, itemHit);
  1633.                 
  1634.                     { Get the horizontal and vertical numbers out of dialog, turn them into Ints. }
  1635.                 GetDItem(pageDialog, kHorItem, kind, itemH, r);
  1636.                 GetIText(itemH, edString);
  1637.                 StringToNum(edString, horPages);
  1638.                 
  1639.                 GetDItem(pageDialog, kVerItem, kind, itemH, r);
  1640.                 GetIText(itemH, edString);
  1641.                 StringToNum(edString, verPages);
  1642.                 
  1643.                     { Done with the input, kill the dialog. }
  1644.                 DisposDialog(pageDialog);
  1645.                 
  1646.                     { Now we need to start up the first document out of the set.  This means we have
  1647.                         to use the current selection coordinates and divide that area into the number of
  1648.                         vertical and horizontal pages desired, giving a final coordinate for the starting
  1649.                         document.  This will perform the extended zoom in. }
  1650.                         
  1651.                     { The basic fractal has been set up.  We now need to change the calculation
  1652.                         area based on the current selection, in order to effect the zoom in. }
  1653.                 WITH  fFracAppDocument.fFracHeader DO BEGIN
  1654.                     oldDeltaP := deltaP;                            { from SELF, the old document. }
  1655.                     oldDeltaQ := deltaQ;
  1656.                     oldRealMin := realMin;
  1657.                     oldImagMin := imagMin;
  1658.                 END;
  1659.  
  1660.                     { calculate new min/max for real and imaginary parts based on how far
  1661.                         into the old fractal plane we were.  This is an extended calculation
  1662.                         since our plane is in extendeds.  We get the new locations of min and
  1663.                         max, and save them off.  We reset the deltaP or Q with SetUpConstants,
  1664.                         in order to force a 1:1 ratio, but we need all sides to determine
  1665.                         which one to force.  Scale up the selection area by 4 since it is in
  1666.                         screen res, not page res. }
  1667.                 WITH  gPageRec  DO BEGIN
  1668.                     RealMin := oldRealMin + oldDeltaP * fSelectionRect.top;
  1669.                     ImagMin := oldImagMin + oldDeltaQ * fSelectionRect.left;
  1670.                     RealMax := oldRealMin + oldDeltaP * fSelectionRect.bottom;
  1671.                     ImagMax := oldImagMin + oldDeltaQ * fSelectionRect.right;
  1672.                 
  1673.                         { Now that we have a extended rectangle defining the area to calculate, we
  1674.                             need to get the next document's extended rectangle by dividing that large
  1675.                             rectangle by the number of pages desired. }
  1676.                     RealMax := RealMin + (RealMax-RealMin)/verPages;
  1677.                     ImagMax := ImagMin + (ImagMax-ImagMin)/horPages;
  1678.                 END;
  1679.                     
  1680.                     { Now we have the page count desired, save it off in the 'page' resource. }
  1681.                 gPageRec.maxH := horPages;
  1682.                 gPageRec.maxV := verPages;
  1683.                 gPageRec.currentH := 1;                            { Start at page 1 on both axes. }
  1684.                 gPageRec.currentV := 1;
  1685.                 
  1686.                 pageH := GetResource('page', kPageId);
  1687.                 FailNil (pageH);
  1688.                 PageRecPtr (pageH^)^ := gPageRec;            { Copy vars into resource. }
  1689.                 
  1690.                     { The resource has changed, mark it as such so it will get saved on a Quit.  This
  1691.                         saves the state information that we are starting afresh.  When a document is
  1692.                         finished, this is changed again to start the next document. }
  1693.                 ChangedResource(pageH);
  1694.                 
  1695.                     { Now we have the actual area to use as the rectangle to calculate, we need to
  1696.                         turn the real numbers into the page rectangle that we will use to print and
  1697.                         save and so on.  This is done by creating the full document in DoMakeDocument,
  1698.                         and the global variables set here will be used there. }
  1699.                 gApplication.OpenNew (aCmdNumber);
  1700.             END;
  1701.             
  1702.         OTHERWISE
  1703.             DoMenuCommand := INHERITED  DoMenuCommand (aCmdNumber);    { next guy in chain. }
  1704.     END;        { CASE on aCmdNumber }
  1705.         
  1706. END;    { TFracAppView.DoMenuCommand }
  1707.  
  1708.  
  1709.     { Set up the New Fractal menus choice in Fractal Menu, based on selection. }
  1710. PROCEDURE TFracAppView.DoSetupMenus;  OVERRIDE;
  1711.  
  1712. BEGIN
  1713.     INHERITED DoSetupMenus;                            { Do mainline stuff first. }
  1714.     
  1715.         { If we have a non-zero selection, then we can enable the menu item to use
  1716.             it as the new fractal dimensions for this document. }
  1717.     Enable (kNewFractal, NOT EmptyRect (fSelectionRect));
  1718.     Enable (kNewMultiPage, NOT EmptyRect (fSelectionRect));
  1719. END;    { TFracAppView.DoSetupMenus }
  1720.  
  1721.  
  1722.     { The way to handle mouse events in the content region of the view.  This will
  1723.         pass back the command object to handle tracking the mouse and creating a
  1724.         new selection in preparation for making a new fractal. }
  1725. FUNCTION  TFracAppView.DoMouseCommand(VAR theMouse: Point; 
  1726.                         VAR info: EventInfo;  VAR hysteresis: Point): TCommand;    OVERRIDE;
  1727.                         
  1728. VAR        tracker:    TAreaSelector;
  1729.         
  1730. BEGIN
  1731.     New(tracker);                                        { make a new command object. }
  1732.     FailNIL(tracker);                                    { no memory, trash out. }
  1733.     tracker.IAreaSelector(SELF, theMouse); { Initialize the command object. }
  1734.     DoMouseCommand := tracker;                { return it for later use. }
  1735. END;        { TFracAppView.DoMouseCommand }
  1736.  
  1737.  
  1738.     { Highlight the current selection rectangle if there is one.  This is drawn in scrCopy
  1739.         mode to make it stand out better when it is a final selection.  XOR is used for
  1740.         the rubberband, until mouseUp. }
  1741. PROCEDURE TFracAppView.DoHighLightSelection(fromHL, toHL: HLState); OVERRIDE;
  1742.  
  1743. VAR        selPatHandle:    PatHandle;
  1744.     
  1745. BEGIN
  1746.     IF toHL = hlOn THEN  BEGIN
  1747.         selPatHandle := GetPattern(kSelPattern);    { get the pattern we use. }
  1748.         IF selPatHandle <> NIL  THEN                         { If pattern available, use it. }
  1749.             PenPat (selPatHandle^^);                            { set pen pattern to our selection kind. }
  1750.         PenMode(srcCopy);                                        { copy mode on pattern selection. }
  1751.         
  1752.             { We have a selection, so go ahead and draw the selection rectangle. }
  1753.         FrameRect (fSelectionRect);                        { outline the frame of selection. }
  1754.     END;        { highlight turned on. }
  1755.     
  1756.         { Turning off the highlight, we need to remove the traces of the selection.
  1757.             To do this, redraw that rectangle. }
  1758.     IF toHL = hlOff THEN   Draw (fSelectionRect);    { ReDraw it to clear selection. }
  1759. END;        { TFracAppView.DoHighlightSelection }
  1760.  
  1761.  
  1762. {-------------------------------  Command  -------------------------------}
  1763.  
  1764.     { Initialize the selector object itself.  Sets up the normal fields. }
  1765. PROCEDURE TAreaSelector.IAreaSelector(ownerView: TFracAppView; startPt: Point);
  1766.  
  1767. BEGIN
  1768.         { initialize normal parts of command }
  1769.     ICommand(cMouseCommand, ownerView.fFracAppDocument, ownerView, NIL);
  1770.  
  1771.     fCausesChange := FALSE;                            { just selection, not changing document. }
  1772.     fCanUndo := FALSE;                                    { therefore, no Undo of no change. }
  1773.     fConstrainsMouse := TRUE;                        { do the constrain to match to screen. }
  1774.     fOwnerView := ownerView;                        { save the view for use in tracking. }
  1775. END;    { TAreaSelector.IAreaSelector }
  1776.  
  1777.  
  1778.     { Track the mouse while the button is down.  This is overridden so we can leave
  1779.         the command object as not having changed, ie. so we can pass back the 
  1780.         gNoChanges as the last step since this is not an undoable operation.  It 
  1781.         doesn't change the view, so we don't need to DoIt or Commit. }
  1782. FUNCTION  TAreaSelector.TrackMouse(aTrackPhase: TrackPhase;
  1783.                         VAR anchorPoint, previousPoint, nextPoint: VPoint;
  1784.                         mouseDidMove: BOOLEAN): TCommand;  OVERRIDE;
  1785.                         
  1786. VAR        selPatHandle:        PatHandle;
  1787.  
  1788. BEGIN
  1789.     TrackMouse := SELF;                                { Assume we are not in release phase. }
  1790.     
  1791.     CASE aTrackPhase OF
  1792.         trackPress:
  1793.         BEGIN
  1794.             fOwnerView.DoHighLightSelection (hlOn, hlOff);        { turn off old selection if any. }
  1795.             fOwnerView.fSelectionRect := gZeroRect;                { clear rect, there isn't one. }
  1796.         END;
  1797.  
  1798.         trackRelease:
  1799.         BEGIN
  1800.             Pt2Rect(VPtToPt(anchorPoint), VPtToPt(nextPoint), fOwnerView.fSelectionRect);
  1801.             fOwnerView.DoHighlightSelection (hlOff, hlOn);        { leave on selection. }
  1802.             TrackMouse := gNoChanges;
  1803.         END;
  1804.     END;    { Case on aTrackPhase }
  1805. END;    { TAreaSelector.TrackMouse }
  1806.  
  1807.  
  1808.     { Track the mouse giving the feedback of a different rectangle kind.  This is so
  1809.         we can use the selection pattern to give a preferred rectangle.  The selection
  1810.         pattern comes out of temporary memory so as to not fail needlessly. }
  1811. PROCEDURE TAreaSelector.TrackFeedback(anchorPoint, nextPoint: VPoint;
  1812.                         turnItOn, mouseDidMove: BOOLEAN);  OVERRIDE;
  1813.                         
  1814. VAR        selBoy:                    Rect;
  1815.             selPatHandle:        PatHandle;
  1816.             
  1817. BEGIN
  1818.     IF mouseDidMove THEN
  1819.         BEGIN {the pen is already in patXOR mode, black, one wide}
  1820.             selPatHandle := GetPattern(kSelPattern);    { get the pattern we use. }
  1821.             IF selPatHandle <> NIL  THEN                        { use our pattern if available. }
  1822.                 PenPat (selPatHandle^^);                            { set pen pattern to our selection kind. }
  1823.         
  1824.             Pt2Rect(VPtToPt(anchorPoint), VPtToPt(nextPoint), selBoy);
  1825.             FrameRect(selBoy);
  1826.         END;
  1827. END;    { TAreaSelector.TrackFeedback }
  1828.  
  1829.  
  1830.     { Constrain the mouse to a rectangle that is the same proportion as the screen, so
  1831.         we can make the selection match better without having to guess at the length
  1832.         or width, or scaling the chosen rect to fit the screen.  Small piece chosen will
  1833.         blow up to fit easily.  This will make it easier to choose a selection that 
  1834.         gives a 1:1 aspect ratio.  This also chooses which direction the mouse has
  1835.         moved, deciding which is larger in order to decide the direction to constrain. }
  1836. PROCEDURE TAreaSelector.TrackConstrain(anchorPoint, previousPoint: VPoint; 
  1837.                             VAR nextPoint: VPoint);  OVERRIDE;
  1838.             
  1839. VAR        newWidth, newHeight:        LongInt;
  1840.             mouseRatio, plotRatio:        Real;
  1841.             constrainRect:                    VRect;
  1842.     
  1843.     PROCEDURE  ChangeWidth;
  1844.     BEGIN
  1845.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1846.                 { Get the new width as a positive number, a displacement that is constrained. }
  1847.             newWidth := ABS (nextPoint.v - anchorPoint.v) * plotWidth DIV plotHeight;
  1848.                 { Decide which quadrant we are in, moving the right direction. }
  1849.             IF nextPoint.h < anchorPoint.h  THEN newWidth := -newWidth;
  1850.                 { Actually change the final point to pass back. }
  1851.             nextPoint.h := anchorPoint.h + newWidth;                        { add offset to get new pt. }                
  1852.         END;
  1853.     END;
  1854.     
  1855.     PROCEDURE  ChangeHeight;
  1856.     BEGIN
  1857.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1858.             newHeight := ABS (nextPoint.h - anchorPoint.h) * plotHeight DIV plotWidth;
  1859.             IF nextPoint.v < anchorPoint.v  THEN newHeight := -newHeight;
  1860.             nextPoint.v := anchorPoint.v + newHeight;                    { add offset to get new pt. }
  1861.         END;
  1862.     END;
  1863.     
  1864.     PROCEDURE  PinPoint;                    { Pin the rectangle to the edge of the document. }
  1865.     BEGIN
  1866.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1867.             SetVRect(constrainRect, 0, 0, plotWidth, plotHeight);
  1868.             PinVRect(constrainRect, nextPoint);
  1869.         END;
  1870.     END;
  1871.  
  1872. BEGIN
  1873.     WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1874.         mouseRatio := ABS ((nextPoint.h - anchorPoint.h)/(nextPoint.v - anchorPoint.v));
  1875.         plotRatio := plotWidth/plotHeight;
  1876.         
  1877.             { The deltaX, deltaY can be thought of as a rect too.  If the ratio of sides on
  1878.                 that rect (width/height) is greater than the ratio of width/height of the
  1879.                 plot rectangle, then we need to grow the height of the rect.  If it is less,
  1880.                 we need to grow the width.  This is a ratio of sides to decide which way
  1881.                 to grow.  We grow to make the new rect still touch the mouse position.
  1882.                 It can be thought of as the rectangle being thicker than tall wanting to 
  1883.                 grow the tall part in a constrained way, and the corollary for the width. }
  1884.         IF mouseRatio > plotRatio  THEN BEGIN        { constrain height to new value. }
  1885.                 ChangeHeight;
  1886.                 PinPoint;
  1887.                 ChangeWidth;
  1888.             END
  1889.         ELSE        BEGIN        { constrain width to new value. }
  1890.                 ChangeWidth;
  1891.                 PinPoint;
  1892.                 ChangeHeight;
  1893.             END;
  1894.     END;    { With }
  1895. END;    { TAreaSelector.TrackConstrain }
  1896.  
  1897.  
  1898. {$POP}        { Restore the compiler state. }
  1899.